﻿
#if false
#define USE_FAKE_PROJECTION
#endif

#if true
#define STUPID_FAST_FPS
#endif

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;

using SunXNA.RainbowXNA.RenderObject;
using SunXNA.CameraManager;

namespace SunXNA.RainbowXNA
{
    partial class CRainbowXNA
    {
        #region Public Classes

        private enum DrawState
        {
            NotRendering = 0,
            InAllState,
            In3DState,
            In2DState,

            NumberOf,
        };

        public class FontHandleType
        {
            public FontHandleType(UInt32 uid)
            {
                m_UID = uid;
            }

            private UInt32 m_UID;

            public UInt32 UID
            {
                get { return m_UID; }
            }
        }

        public class RenderObjectHandleType
        {
            private UInt32 m_UID;
            private CRainbowXNA m_renderer;

            public RenderObjectHandleType(UInt32 uid, CRainbowXNA renderer)
            {
                m_UID = uid;
                m_renderer = renderer;
            }

            public UInt32 UID
            {
                get { return m_UID; }
            }

            public BoundingSphere GraphicsVolume
            {
                get
                {
                    return m_renderer.GetObjectVolume(this);
                }
            }

            public BoundingBox GraphicsBox
            {
                get
                {
                    return m_renderer.GetObjectBox(this);
                }
            }

            public IBaseRenderObject RenderObject
            {
                get
                {
                    return m_renderer.GetRenderObject(this);
                }
            }
        }

        public class ViewportObjectHandleType
        {
            private UInt32 m_UID;
            private CViewport m_viewport;

            public ViewportObjectHandleType(UInt32 uid, CViewport viewport)
            {
                m_UID = uid;
                m_viewport = viewport;
            }

            public UInt32 UID
            {
                get { return m_UID; }
            }

            public CViewport Viewport
            {
                get
                {
                    return m_viewport;
                }
            }
        }

        public class TextureObjectHandleType
        {
            private UInt32 m_UID;
            private CRainbowXNA m_renderer;

            public TextureObjectHandleType(UInt32 uid, CRainbowXNA renderer)
            {
                m_UID = uid;
                m_renderer = renderer;
            }

            public UInt32 UID
            {
                get
                {
                    return m_UID;
                }
            }

            public Texture2D Texture
            {
                get
                {
                    return m_renderer.GetTexture(this);
                }
            }
        }

        #endregion

        #region Variables

        private GraphicsDeviceManager m_deviceManager;
        private SpriteBatch m_spriteBatch;
        private Color m_clearColour;
        private Game m_user;
        private DrawState m_eCurrentRenderState;
        private BoundingBox m_sceneSize;

        private float m_screenWidth;
        private float m_screenHeight;

        private float m_aspectRatio;
        private UInt32 m_lastUID;

        private Dictionary<UInt32, SpriteFont> m_Fonts;
        private Dictionary<UInt32, IBaseRenderObject> m_RenderObjects;

        private Dictionary<UInt32, CViewport> m_Viewports;
        private Viewport m_defaultViewport;

        private Dictionary<UInt32, Texture2D> m_Textures;

        private CRenderQuadTreeNode m_quadTreeRoot;

        private Batch.CBatchManager m_batchManager;
        private Material.CMaterialManager m_materialManager;

        private Skybox.CSkybox m_skyBox;

        //
        //  Test
        //

        public int totalObjects = 0;
        public int objectsRendered = 0;

#if false
        Model fail;
        SkinnedModel.AnimationPlayer animationPlayer;

        void TestCreate()
        {
            ///////
            fail = m_user.Content.Load<Model>("Models/Beast/EnemyBeast");

            // Look up our custom skinning information.
            SkinnedModel.SkinningData skinningData = fail.Tag as SkinnedModel.SkinningData;

            if (skinningData == null)
                throw new InvalidOperationException
                    ("This model does not contain a SkinningData tag.");

            // Create an animation player, and start decoding an animation clip.
            animationPlayer = new SkinnedModel.AnimationPlayer(skinningData);

            SkinnedModel.AnimationClip clip = skinningData.AnimationClips["Take 001"];

            animationPlayer.StartClip(clip);
            //animationPlayer.
            //////
        }

        void TestRender(Matrix m4View, Matrix m4Projection)
        {
            Matrix[] bones = animationPlayer.GetSkinTransforms();

            // Render the skinned mesh.
            foreach (ModelMesh mesh in fail.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["Bones"].SetValue(bones);
                    effect.Parameters["m4View"].SetValue(m4View);
                    effect.Parameters["m4Projection"].SetValue(m4Projection);
                }

                mesh.Draw();
            }
        }

        public void TestUpdate(TimeSpan timeElapsed)
        {
            animationPlayer.Update(timeElapsed, true, Matrix.Identity);
        }
#endif

        //
        //  End test
        //

        #endregion

        #region Properties

        public Color ClearColor
        {
            get
            {
                return m_clearColour;
            }

            set
            {
                m_clearColour = value;
            }
        }

        public BoundingBox SceneSize
        {
            get
            {
                return m_sceneSize;
            }
        }

        #endregion

        #region Functions

        public CRainbowXNA()
        {
            m_deviceManager = null;
            m_spriteBatch = null;
            m_user = null;
            m_lastUID = 0;

            m_eCurrentRenderState = DrawState.NotRendering;

            m_Fonts = new Dictionary<uint, SpriteFont>();
            m_RenderObjects = new Dictionary<uint, IBaseRenderObject>();

            m_Viewports = new Dictionary<uint, CViewport>();
            m_Textures = new Dictionary<uint, Texture2D>();

            m_quadTreeRoot = null;

            m_batchManager = new Batch.CBatchManager();
            m_materialManager = new Material.CMaterialManager();
        }

        public void Create( Game game, int iScreenWidth, int iScreenHeight, bool bIsFullScreen, BoundingBox sceneSize )
        {
            m_user = game;
            m_deviceManager = new GraphicsDeviceManager(m_user);

            m_deviceManager.IsFullScreen = bIsFullScreen;

            m_deviceManager.PreferredBackBufferHeight = iScreenHeight;
            m_deviceManager.PreferredBackBufferWidth = iScreenWidth;
            m_deviceManager.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;

            m_deviceManager.MinimumVertexShaderProfile = ShaderProfile.VS_2_0;

#if STUPID_FAST_FPS
            m_deviceManager.SynchronizeWithVerticalRetrace = false;
#endif

            m_deviceManager.ApplyChanges();

            m_clearColour = Color.Black;

            m_screenHeight = iScreenHeight;
            m_screenWidth = iScreenWidth;

            m_aspectRatio = m_screenWidth / m_screenHeight;

            m_sceneSize = sceneSize;

            m_quadTreeRoot = new CRenderQuadTreeNode(sceneSize, 4);
            m_materialManager.Initialise(m_user.Content);
            m_batchManager.Initialise(m_materialManager);

            m_spriteBatch = new SpriteBatch(m_deviceManager.GraphicsDevice);
            m_defaultViewport = m_deviceManager.GraphicsDevice.Viewport;

            // Init particle system
            Singleton<Darwin.Source.Particle_Systems.CParticleManager>.Create();
            Singleton<Darwin.Source.Particle_Systems.CParticleManager>.Instance.Initialise(m_user, sceneSize.Min.Y);
        }

        public FontHandleType CreateFont(string FontFileName)
        {
            SpriteFont newFont = m_user.Content.Load<SpriteFont>(FontFileName);
            m_Fonts.Add(++m_lastUID, newFont);
            return new FontHandleType(m_lastUID);
        }

        public void DeleteFont(FontHandleType handle)
        {
            m_Fonts.Remove(handle.UID);
        }

        public CRenderInstance CreateRenderInstance(RenderObjectHandleType handle)
        {
            CRenderInstance instance = null;
            IBaseRenderObject renderObject = null;

            if (m_RenderObjects.TryGetValue(handle.UID, out renderObject))
            {
                instance = renderObject.CreateDefaultInstance(handle,this);
            }

            return instance;
        }

        public RenderObjectHandleType LoadStaticMesh( string AssetName )
        {
            CStaticMeshRenderObject newMesh = new CStaticMeshRenderObject(m_user.Content, AssetName,this);
            m_RenderObjects.Add(++m_lastUID, newMesh);
            return new RenderObjectHandleType(m_lastUID, this);
        }
        public RenderObjectHandleType LoadAnimatedMesh(string AssetName)
        {
            CAnimatedMeshRenderObject newMesh = new CAnimatedMeshRenderObject(m_user.Content, AssetName, this);
            m_RenderObjects.Add(++m_lastUID, newMesh);
            return new RenderObjectHandleType(m_lastUID, this);
        }

        public RenderObjectHandleType HandleMeshPart( ModelMesh mesh, ModelMeshPart meshPart )
        {
            CModelMeshPartRenderObject newMesh = new CModelMeshPartRenderObject(mesh, meshPart);
            m_RenderObjects.Add(++m_lastUID, newMesh);
            return new RenderObjectHandleType(m_lastUID, this);
        }

        public RenderObjectHandleType LoadTerrain(CHeightMap heightmap, int offsetX, int offsetZ, int vertsWide, int vertsDeep, float cellWidth, float cellDepth)
        {
            CTerrainPatchRenderObject newTerrain = new CTerrainPatchRenderObject(m_deviceManager.GraphicsDevice, heightmap, offsetX, offsetZ, vertsWide, vertsDeep, cellWidth, cellDepth);
            m_RenderObjects.Add(++m_lastUID, newTerrain);
            return new RenderObjectHandleType(m_lastUID, this);
        }

        public RenderObjectHandleType CreatePlane(float width, float depth)
        {
            CPlaneRenderObject newPlane = new CPlaneRenderObject(m_deviceManager.GraphicsDevice, width, depth);
            m_RenderObjects.Add(++m_lastUID, newPlane);
            return new RenderObjectHandleType(m_lastUID, this);
        }

#if DEBUG_GRAPHICS_ENABLED
        public RenderObjectHandleType CreateObject(VertexPositionColor[] verts, PrimitiveType primitiveType )
        {
            CVertexRenderObject newObject = new CVertexRenderObject(verts, m_deviceManager.GraphicsDevice, primitiveType);
            m_RenderObjects.Add(++m_lastUID, newObject);
            return new RenderObjectHandleType(m_lastUID, this);
        }
#endif
        public IBaseRenderObject GetRenderObject(RenderObjectHandleType handle)
        {
            IBaseRenderObject renderObject = null;
            Trace.Assert(m_RenderObjects.TryGetValue(handle.UID, out renderObject));

            return renderObject;
        }

        public void DeleteRenderObject(RenderObjectHandleType handle)
        {
            m_RenderObjects.Remove(handle.UID);
        }

        public void SetSkybox(string AssetName)
        {
            m_skyBox = new SunXNA.RainbowXNA.Skybox.CSkybox(m_user.Content, AssetName);
        }

        public BoundingSphere GetObjectVolume(RenderObjectHandleType handle)
        {
            IBaseRenderObject renderObject = null;
            Trace.Assert(m_RenderObjects.TryGetValue(handle.UID, out renderObject));

            return renderObject.Volume;
        }

        public BoundingBox GetObjectBox(RenderObjectHandleType handle)
        {
            IBaseRenderObject renderObject = null;
            Trace.Assert(m_RenderObjects.TryGetValue(handle.UID, out renderObject));

            return renderObject.Box;
        }

        public ViewportObjectHandleType CreateViewport(Rectangle area, CCameraManager.CameraHandle cameraHandle, Color clearColor)
        {
            CViewport newViewport = new CViewport(area, cameraHandle, clearColor);
            m_Viewports.Add(++m_lastUID, newViewport);
            return new ViewportObjectHandleType(m_lastUID, newViewport);
        }

        public ViewportObjectHandleType CreateViewport(Rectangle area, CCameraManager.CameraHandle cameraHandle)
        {
            CViewport newViewport = new CViewport(area, cameraHandle, ClearColor);
            m_Viewports.Add(++m_lastUID, newViewport);
            return new ViewportObjectHandleType(m_lastUID, newViewport);
        }

        public TextureObjectHandleType LoadTexture(string AssetName)
        {
            Texture2D newTexture = m_user.Content.Load<Texture2D>(AssetName);

            if (!m_Textures.ContainsValue(newTexture))
            {
                newTexture.GenerateMipMaps(TextureFilter.Anisotropic);
                m_Textures.Add(++m_lastUID, newTexture);
                return new TextureObjectHandleType(m_lastUID, this);
            }
            else
            {
                foreach (KeyValuePair<UInt32, Texture2D> pair in m_Textures)
                {
                    if (pair.Value == newTexture)
                    {
                        return new TextureObjectHandleType(pair.Key, this);
                    }
                }
                Trace.Fail("WTF?");
                return null;
            }
        }

        public TextureObjectHandleType CreateBlankTexture(int width, int height)
        {
            Texture2D newTexture = new Texture2D(m_deviceManager.GraphicsDevice,
                                                 width, height,
                                                 1, TextureUsage.None, SurfaceFormat.Vector4);
            m_Textures.Add(++m_lastUID, newTexture);
            return new TextureObjectHandleType(m_lastUID, this);
        }

        public TextureObjectHandleType HandleTexture(Texture2D newTexture)
        {
            if (!m_Textures.ContainsValue(newTexture))
            {
                newTexture.GenerateMipMaps(TextureFilter.Anisotropic);
                m_Textures.Add(++m_lastUID, newTexture);
                return new TextureObjectHandleType(m_lastUID, this);
            }
            else
            {
                foreach (KeyValuePair<UInt32, Texture2D> pair in m_Textures)
                {
                    if (pair.Value == newTexture)
                    {
                        return new TextureObjectHandleType(pair.Key, this);
                    }
                }
                Trace.Fail("WTF?");
                return null;
            }
        }

        public Texture2D GetTexture(TextureObjectHandleType textureHandle)
        {
            Texture2D texture = null;
            if (m_Textures.TryGetValue(textureHandle.UID, out texture))
            {
                return texture;
            }

            return null;
        }

        public void DeleteViewport(ViewportObjectHandleType handle)
        {
            m_Viewports.Remove(handle.UID);
        }

        public void Clear()
        {
            m_Fonts.Clear();
            m_RenderObjects.Clear();
            m_Viewports.Clear();

            m_quadTreeRoot.Clear();
            m_batchManager.Clear();
        }

        public void BeginAll()
        {
            Trace.Assert(m_eCurrentRenderState == DrawState.NotRendering);

            m_deviceManager.GraphicsDevice.Viewport = m_defaultViewport;
            m_deviceManager.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, m_clearColour, 1.0f, 0);
            m_eCurrentRenderState = DrawState.InAllState;

            totalObjects = 0;
            objectsRendered = 0;

            m_quadTreeRoot.Clear();
            m_batchManager.Clear();
        }

        public void Begin3D()
        {
            Trace.Assert(m_eCurrentRenderState == DrawState.InAllState);
            m_eCurrentRenderState = DrawState.In3DState;
        }

        public void AddPotentialVisibleObject(CRenderInstance drawPacket)
        {
            ++totalObjects;
            m_quadTreeRoot.Add(drawPacket.GraphicsBox, drawPacket);
        }

        public void End3D(float timeInSeconds, float drawTime)
        {

            //m_deviceManager.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

            totalObjects *= m_Viewports.Count;
            float stampIncrement = drawTime / (float)m_Viewports.Count; //HACK HACK HACK

            foreach (KeyValuePair<UInt32, CViewport> currentViewportPair in m_Viewports)
            {
                Matrix m4Projection;
                Matrix m4View;
                currentViewportPair.Value.CalculateMatrices(out m4Projection, out m4View);

#if USE_FAKE_PROJECTION
                float FOV = currentViewportPair.Value.CameraHandle.Camera.FOV;
                float ar = currentViewportPair.Value.AspectRatio;

                FOV /= 3.0f;
                Matrix m4FakeProjection = Matrix.CreatePerspectiveFieldOfView( FOV, ar, 1.0f, 1000.0f );

                BoundingFrustum frustrum = new BoundingFrustum(m4View * m4FakeProjection);
#else
                BoundingFrustum frustrum = currentViewportPair.Value.Frustrum;
#endif

                m_deviceManager.GraphicsDevice.Viewport = currentViewportPair.Value.XNAViewport;

                //
                //  Clear the viewport
                //
                m_deviceManager.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, currentViewportPair.Value.ClearColour, 1.0f, 0);

                m_batchManager.Clear();
                objectsRendered += m_quadTreeRoot.Draw(timeInSeconds, frustrum, ref m4View, ref m4Projection, m_deviceManager.GraphicsDevice, m_batchManager);

                m_skyBox.Draw(m_deviceManager.GraphicsDevice, ref m4View, ref m4Projection);

                GraphicsDevice device = m_deviceManager.GraphicsDevice;
                m_batchManager.Draw(ref m4View, ref m4Projection, ref device, ref m_RenderObjects, ref m_Textures);
                //TestRender(m4View, m4Projection);

                Singleton<Darwin.Source.Particle_Systems.CParticleManager>.Instance.DoRender(m4Projection, m4View);

                timeInSeconds += stampIncrement;    //HACK HACK HACK
            }

            Trace.Assert(m_eCurrentRenderState == DrawState.In3DState);
            m_eCurrentRenderState = DrawState.InAllState;
            m_deviceManager.GraphicsDevice.Viewport = m_defaultViewport;
            m_deviceManager.GraphicsDevice.RenderState.FillMode = FillMode.Solid;
        }

        public void Begin2D()
        {
            Trace.Assert(m_eCurrentRenderState == DrawState.InAllState);

            m_eCurrentRenderState = DrawState.In2DState;
            m_spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);
        }

        public void RenderText(FontHandleType fontHandle, string Text, Vector2 v2Position, Color colour)
        {
            Trace.Assert(m_eCurrentRenderState == DrawState.In2DState);

            SpriteFont font = null;
            Trace.Assert(m_Fonts.TryGetValue(fontHandle.UID, out font));

            m_spriteBatch.DrawString(font, Text, v2Position, colour);
        }

        public void RenderTexture(TextureObjectHandleType handle)
        {
            Texture2D texture = m_Textures[handle.UID];
            Rectangle destination;
            destination.X = 0;
            destination.Y = 0;
            destination.Width = texture.Width;
            destination.Height = texture.Height;

            m_spriteBatch.Draw(texture, destination, Color.White);
        }

        public void RenderTexture(TextureObjectHandleType handle, ref Vector2 v2Position)
        {
            Texture2D texture = m_Textures[handle.UID];
            Rectangle destination;
            destination.X = (int)v2Position.X;
            destination.Y = (int)v2Position.Y;
            destination.Width = texture.Width;
            destination.Height = texture.Height;

            m_spriteBatch.Draw(texture, destination, Color.White);
        }

        public void RenderTexture(TextureObjectHandleType handle, ref Vector2 v2Position, ref Rectangle sourceArea)
        {
            Texture2D texture = m_Textures[handle.UID];
            Rectangle destination;
            destination.X = (int)v2Position.X;
            destination.Y = (int)v2Position.Y;
            destination.Width = sourceArea.Width;
            destination.Height = sourceArea.Height;

            m_spriteBatch.Draw(texture, destination, sourceArea, Color.White);
        }

        public void End2D()
        {
            
            Trace.Assert(m_eCurrentRenderState == DrawState.In2DState);

            m_spriteBatch.End();
            m_eCurrentRenderState = DrawState.InAllState;
        }

        public void EndAll()
        {
            Trace.Assert(m_eCurrentRenderState == DrawState.InAllState);

            m_deviceManager.GraphicsDevice.Present();
            m_eCurrentRenderState = DrawState.NotRendering;
        }

        #endregion
    }
}
